import java.awt.*;
import java.util.ArrayList;
import java.util.concurrent.ThreadLocalRandom;

public class Simulation {

    // gravitational constant
    public static final double G = 6.6743e-11;
    // approximation ratio, less means less accurate
    public static final double T = 1;
    // random body count
    public static final double N = 10000;

    // one astronomical unit (AU) is the average distance of earth to the sun.
    public static final double AU = 150e9;

    public static void main(String[] args) {
        CelestialBody sun1 = new CelestialBody(
                "Sol A",
                1.989e36,
                696340e3,
                new Vector3(AU, 0, 0),
                new Vector3(0, 0, 0),
                StdDraw.YELLOW
        );
        CelestialBody sun2 = new CelestialBody(
                "Sol B",
                1.989e36,
                696340e3,
                new Vector3(-AU, -AU * .5d, 0),
                new Vector3(0, 0, 0),
                StdDraw.YELLOW
        );
        CelestialBody sun3 = new CelestialBody(
                "Sol C",
                1.989e36,
                696340e3,
                new Vector3(-AU, AU * .5d, 0),
                new Vector3(0, 0, 0),
                StdDraw.YELLOW
        );

        StdDraw.setCanvasSize(500, 500);
        StdDraw.setXscale(-2 * AU, 2 * AU);
        StdDraw.setYscale(-2 * AU, 2 * AU);
        StdDraw.enableDoubleBuffering();
        StdDraw.clear(StdDraw.BLACK);

        int seconds = 0;

        Color[] colors = new Color[]{StdDraw.WHITE, StdDraw.YELLOW, StdDraw.RED, StdDraw.GREEN, StdDraw.BLUE, StdDraw.PINK, StdDraw.CYAN, StdDraw.MAGENTA, StdDraw.ORANGE, StdDraw.BOOK_LIGHT_BLUE};

        ArrayList<CelestialBody> bodies = new ArrayList<>();
        bodies.add(sun1);
        bodies.add(sun2);
        bodies.add(sun3);
        for(int i = 0; i < N; i++) {
            int j = i % 10;
            CelestialBody body = new CelestialBody(randMass(), randPos(), randMov(), colors[j]);
            bodies.add(body);
        }

        long construct, force, draw, end;


        while (true) {

            construct = System.currentTimeMillis();
            OctTree tree = new OctTree(4*AU);
            tree.construct(bodies);
            force = System.currentTimeMillis();
            tree.force(bodies);
            // show all movements in StdDraw canvas only every 3 hours (to speed up the simulation)
            if (seconds % (20) == 0) {
                draw = System.currentTimeMillis();
                // clear old positions (exclude the following line if you want to draw orbits).
                StdDraw.clear(StdDraw.BLACK);

                tree.draw();
                end = System.currentTimeMillis();
                StdDraw.setPenColor(StdDraw.WHITE);
                StdDraw.textRight(2*AU, 2*AU - 1e10d, String.format("(%d s) %d frame", seconds, (seconds % (60*3*3600))/(3*3600) + 1));
                StdDraw.textRight(2*AU - 7e10d, 2*AU - 3e10d, String.format("%d ms", force - construct));
                StdDraw.textRight(2*AU - 7e10d, 2*AU - 5e10d, String.format("%d ms", draw - force));
                StdDraw.textRight(2*AU - 7e10d, 2*AU - 7e10d, String.format("%d ms", end - draw));
                StdDraw.textRight(2*AU, 2*AU - 3e10d, "dBuild");
                StdDraw.textRight(2*AU, 2*AU - 5e10d, "dForce");
                StdDraw.textRight(2*AU, 2*AU - 7e10d, "dDraw");


                // show new positions
                StdDraw.show();
            }

            seconds++; // each iteration computes the movement of the celestial bodies within one second.

        }
    }

    private static double randMass() {
        return Math.random() * 1e30;
    }

    private static Vector3 randPos() {

        double x = ThreadLocalRandom.current().nextDouble(-(2*AU), (2*AU));
        double y = ThreadLocalRandom.current().nextDouble(-(2*AU), (2*AU));
        double z = 0;
        return new Vector3(x,y,z);
    }

    private static Vector3 randMov() {
        double x = ThreadLocalRandom.current().nextDouble(-(1e5), (1e5));
        double y = ThreadLocalRandom.current().nextDouble(-(1e5), (1e5));
        double z = 0;

        return new Vector3(x,y,z);
    }
}
